'use strict';

import FsExt from 'fs-extra';
import Sanitize from 'sanitize-filename';

const path = require('psth');
const Noop = function () {};

class CacheFile {
  /**
   * CacheFile constructor
   * @param {Object} options
   * @param {String} options.tmpDir
   * @api public
   */
  constructor(options) {
    let self = this;
    self.tmpDir = options.tmpDir || path.join(process.cwd(), 'tmp');

    if (!FsExt.existsSync(self.tmpDir)) FsExt.mkdirSync(self.tmpDir);

    let cacheFiles = FsExt.readdirSync(self.tmpDir);
    self.cache = {};
    cacheFiles.forEach(function (file) {
      file = file.replace('.json', '');
      self.cache[file] = true;
    });
  }

  /**
   * Get entry
   * @param {Number} key
   * @param {Function} callback
   * @api public
   */
  // wait(mils) {
  //   let now = new Date;
  //   while (new Date - now <= mils);
  // };

  /**
   * Get entry
   * @param {String} key
   * @param {Function} callback
   * @api public
   */
  get(key, callback) {
    let self = this;
    let val = null;
    let data = null;
    key = Sanitize(key);
    let cacheFile = path.join(self.tmpDir, key + '.json');

    callback = callback || Noop;

    if (FsExt.existsSync(cacheFile)) {
      data = FsExt.readFileSync(cacheFile);
      data = JSON.parse(data);
    } else {
      callback(null, null);
      return null;
    }

    if (!this.cache[key]) {
      callback(null, null);
      return null;
    }

    if (!data) {
      callback(null, data);
      return null;
    }
    if (data.expire < Date.now()) {
      this.del(key);
      callback(null, null);
      return null;
    }

    try {
      val = JSON.parse(data.value);
    } catch (e) {
      callback(e);
      return null;
    }

    return val;

    process.nextTick(function tick() {
      callback(null, val);
    });

  };

  /**
   * put an entry.
   * @param {String} key
   * @param {Mixed} val
   * @param {Number} ttl
   * @param {Function} expireCallback
   * @param {Function} callback
   * @api public
   */
  put(key, val, ttl, expireCallback, callback) {
    let record = {};
    let self = this;

    if (typeof val === 'undefined' || null) return callback(new Error('val not put'));
    if (typeof ttl === 'function') callback = ttl;
    callback = callback || Noop;
    ttl = ttl * 1000 || 60 * 1000;

    try {
      record = {
        value: JSON.stringify(val),
        expire: JSON.stringify(Date.now() + ttl)
      };
    } catch (e) {
      return callback(e);
    }

    key = Sanitize(key);
    let cacheFile = path.join(self.tmpDir, key + '.json');

    FsExt.writeFileSync(cacheFile, JSON.stringify(record, null, 4));

    process.nextTick(function tick() {
      self.cache[key] = record.expire;
      callback(null, val);
    });

    if (!isNaN(record.expire)) {
      record.timeout = setTimeout(function () {
        self.del(key);
        if (expireCallback) {
          expireCallback(key, val);
        }
      }.bind(this), ttl);
    }
  };


  /**
   * Delete an entry.
   * @param {String} key
   * @param {Function} callback
   * @api public
   */
  del(key, callback) {
    let self = this;
    key = Sanitize(key);
    let cacheFile = path.join(self.tmpDir, key + '.json');

    callback = callback || Noop;

    if (!FsExt.existsSync(cacheFile)) {
      self.cache[key] = null;
      return callback();
    }

    try {
      FsExt.removeSync(cacheFile);
    } catch (e) {
      return callback(e);
    }

    process.nextTick(function tick() {
      self.cache[key] = null;
      callback(null);
    });
  };

  /**
   * Clear all cached files
   * @param {String} key
   * @param {Function} callback
   * @api public
   */
  clear(key, callback) {
    let self = this;

    if ('function' === typeof key) {
      callback = key;
      key = null;
    }

    callback = callback || Noop;

    try {
      FsExt.removeSync(self.tmpDir);
      FsExt.mkdirSync(self.tmpDir);
    } catch (e) {
      return callback(e);
    }

    process.nextTick(function tick() {
      self.cache = {};
      callback(null);
    });
  };


  /**
   * Get all cached entries
   * @param {Function} callback
   */
  getAll(callback) {
    let self = this;
    let entries = [];
    let cache = self.cache;

    Object.keys(cache).forEach(function (entry) {
      self.get(entry, function (err, result) {
        if (err) return callback(err);
        entries.push(result);
      });
    });

    process.nextTick(function () {
      callback(null, entries);
    });
  };
}

var cache = new CacheFile({
  tmpDir: './cache'
});

export default CacheFile;
export {
  CacheFile,
  cache
};
